(function ($) {
    var
        document = window.document,
        key,
        name,
        rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
        scriptTypeRE = /^(?:text|application)\/javascript/i,
        xmlTypeRE = /^(?:text|application)\/xml/i,
        jsonType = 'application/json',
        htmlType = 'text/html',
        blankRE = /^\s*$/,
        escape = encodeURIComponent,
        originAnchor = document.createElement('a');

    originAnchor.href = window.location.href;

    // Empty function, used as default callback
    function emptyFn() { }

    var ajaxSettings = {
        // Default type of request
        type: 'GET',
        // Callback that is executed before request
        beforeSend: emptyFn,//fn(data, type)
        // Callback that is executed if the request succeeds
        success: emptyFn, //fn(data, status, xhr)
        // Callback that is executed the the server drops error
        error: emptyFn,//fn(xhr, type, error)
        // Callback that is executed on request complete (both: error and success)
        complete: emptyFn,//fn(type, xhr, settings)
        // Transport
        xhr: function () {
            return new window.XMLHttpRequest();
        },
        // MIME types mapping
        // IIS returns Javascript as "application/x-javascript"
        accepts: {
            script: 'text/javascript, application/javascript, application/x-javascript',
            json: jsonType,
            xml: 'application/xml, text/xml',
            html: htmlType,
            text: 'text/plain'
        },
        // Whether the request is to another domain
        crossDomain: false,
        // Default timeout
        timeout: 0,
        onErrorEval:false,
        // Whether data should be serialized to string
        processData: true,
        // Whether the browser should be allowed to cache GET responses
        cache: true,
        //Used to handle the raw response data of XMLHttpRequest.
        //This is a pre-filtering function to sanitize the response.
        //The sanitized response should be returned
        dataFilter: emptyFn
    };

    // triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
    function ajaxBeforeSend(xhr, settings) {
        if (settings.beforeSend(xhr, settings) === false) {
            return false;
        }
        //triggerGlobal(settings, context, 'ajaxSend', [xhr, settings]);
    }
    function ajaxSuccess(data, xhr, settings) {
        var status = 'success';
        settings.success(data, status, xhr);
        settings.complete(status, xhr, settings);
    }
    // type: "timeout", "error", "abort", "parsererror"
    function ajaxError(error, type, xhr, settings) {
        settings.error(xhr, type, error);
        settings.complete(type, xhr, settings);
    }

    function ajaxDataFilter(data, type, settings) {
        if (settings.dataFilter === emptyFn) {
            return data;
        }
        return settings.dataFilter(data, type);
    }

    function mimeToDataType(mime) {
        if (mime) {
            mime = mime.split(';', 2)[0];
        }
        return mime && (mime === htmlType ? 'html' :
            mime === jsonType ? 'json' :
                scriptTypeRE.test(mime) ? 'script' :
                    xmlTypeRE.test(mime) && 'xml') || 'text';
    }

    function appendQuery(url, query) {
        if (query === '') {
            return url;
        }
        return (url + '&' + query).replace(/[&?]{1,2}/, '?');
    }

    function serialize(params, obj, traditional, scope) {
        var type,
            array = $B.isArrayFn(obj),
            hash = $B.isPlainObjectFn(obj);
        var keys = Object.keys(obj);
        for(let i =0 ; i < keys.length ;i++){
            let key = keys[i];
            let value = obj[key];
            if (scope) {
                key = traditional ? scope :
                    scope + '[' + (hash || type === 'object' || type === 'array' ? key : '') + ']';
            }
            // handle data in serializeArray() format
            if (!scope && array) {
                params._addFN(value.name, value.value);
            } else if (type === "array" || (!traditional && type === "object")) {// recurse into nested objects
                serialize(params, value, traditional, key);
            } else {
                params._addFN(key, value);
            }
        }        
    }

    function object2param(obj, traditional) {
        var params = [];
        params._addFN = function (key, value) {
            if (typeof value === "function") {
                value = value();
            }
            if (value === null) {
                value = "";
            }
            this.push(escape(key) + '=' + escape(value));
        };
        serialize(params, obj, traditional);
        params._addFN = undefined;
        return params.join('&').replace(/%20/g, '+');
    }

    // serialize payload and append it to the URL for GET requests
    function serializeData(options) {       
        if (options.processData && options.data && typeof (options.data) !== "string") {
            options.data = object2param(options.data, options.traditional);
        }
        if (options.data && (!options.type || options.type.toUpperCase() === 'GET' || 'jsonp' === options.dataType)) {
            options.url = appendQuery(options.url, options.data);
            options.data = undefined;
        }  
    }


    $.ajaxJSONP = function (options) {
        if (!('type' in options)) {
            return $.ajax(options);
        }
        var jsonpID = +new Date();
        var _callbackName = options.jsonpCallback,
            callbackName = ($B.isFunctionFn(_callbackName) ? _callbackName() : _callbackName) || ('Zepto' + (jsonpID++)),
            scriptEl = document.createElement('script'),
            originalCallback = window[callbackName],
            responseData,
            abort = function (errorType) {
                $B.DomUtils.trigger(scriptEl,"error");
                //$(script).triggerHandler('error', errorType || 'abort')
            },
            xhr = { abort: abort },
            abortTimeout;


        var scriptFn = function (e, errorType) {
            clearTimeout(abortTimeout);
            $B.DomUtils.offEvents(scriptEl);
            if (e.type === 'error' || !responseData) {
                //error, type, xhr, settings
                ajaxError(null, errorType || 'error', xhr, options);
            } else {
                ajaxSuccess(responseData[0], xhr, options);
            }

            window[callbackName] = originalCallback;
            if (responseData && $B.isFunctionFn(originalCallback)) {
                originalCallback(responseData[0]);
            }
            originalCallback = responseData = undefined;
            $B.DomUtils.remove(scriptEl);
        };
        //$(script).on('load error', );
        $B.DomUtils.addListener(scriptEl, "load", scriptFn);
        $B.DomUtils.addListener(scriptEl, "error", scriptFn);

        if (ajaxBeforeSend(xhr, options) === false) {
            abort('abort');
            return xhr;
        }

        window[callbackName] = function () {
            responseData = arguments;
        };

        scriptEl.src = options.url.replace(/\?(.+)=\?/, '?$1=' + callbackName);
        document.head.appendChild(scriptEl);

        if (options.timeout > 0) {
            let timeout = options.timeout * 1000;
            abortTimeout = setTimeout(() => {
                abort('timeout');
            }, timeout);
            return xhr;
        }
    };
    $.ajax = function (options) {
        var settings = $B.extendObjectFn({}, ajaxSettings, options || {}),
            urlAnchor, hashIndex;

        if (!settings.crossDomain) {
            urlAnchor = document.createElement('a');
            urlAnchor.href = settings.url;
            // cleans up URL for .href (IE only), see https://github.com/madrobby/zepto/pull/1049
            urlAnchor.href = urlAnchor.href;
            settings.crossDomain = (originAnchor.protocol + '//' + originAnchor.host) !== (urlAnchor.protocol + '//' + urlAnchor.host);
        }

        if (!settings.url) {
            settings.url = window.location.toString();
        }

        if ((hashIndex = settings.url.indexOf('#')) > -1) {
            settings.url = settings.url.slice(0, hashIndex);
        }
        if( settings.contentType === "application/json"){
            settings.processData = false;
        }

        serializeData(settings);

        var dataType = settings.dataType,
            hasPlaceholder = /\?.+=\?/.test(settings.url);
        if (hasPlaceholder) {
            dataType = 'jsonp';
        }

        if (settings.cache === false || ((!options || options.cache !== true) && ('script' === dataType || 'jsonp' === dataType))) {
            settings.url = appendQuery(settings.url, '_=' + Date.now());
        }


        if ('jsonp' === dataType) {
            if (!hasPlaceholder) {
                settings.url = appendQuery(settings.url, settings.jsonp ? (settings.jsonp + '=?') : settings.jsonp === false ? '' : 'callback=?');
            }
            return $.ajaxJSONP(settings);
        }

        var mime = settings.accepts[dataType],
            headers = {},
            protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
            xhr = settings.xhr(),
            nativeSetHeader = xhr.setRequestHeader,
            abortTimeout;
        var setHeader = function (name, value) {
            headers[name.toLowerCase()] = [name, value];
        };

        if (!settings.crossDomain) {
            setHeader('X-Requested-With', 'XMLHttpRequest');
        }
        setHeader('Accept', mime || '*/*');
        if (mime = settings.mimeType || mime) {
            if (mime.indexOf(',') > -1) {
                mime = mime.split(',', 2)[0];
            }
            xhr.overrideMimeType && xhr.overrideMimeType(mime);
        }
        if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() !== 'GET')) {
            setHeader('Content-Type', settings.contentType || 'application/x-www-form-urlencoded');
        }
        if (settings.headers) {
            let keys = settings.headers;
            keys.forEach(function (key) {
                setHeader(key, settings.headers[key]);
            });
        }
        xhr.setRequestHeader = setHeader;
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                xhr.onreadystatechange = emptyFn;
                clearTimeout(abortTimeout);
                var result,
                    error = false;
                if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || (xhr.status === 0 && protocol === 'file:')) {
                    dataType = dataType || mimeToDataType(settings.mimeType || xhr.getResponseHeader('content-type'));
                    if (xhr.responseType === 'arraybuffer' || xhr.responseType === 'blob') {
                        result = xhr.response;
                    } else {
                        result = xhr.responseText;
                        try {
                            // http://perfectionkills.com/global-eval-what-are-the-options/
                            // sanitize response accordingly if data filter callback provided
                            result = ajaxDataFilter(result, dataType, settings);
                            if (dataType === 'script') {
                                //(1, eval)(result);
                            } else if (dataType === 'xml') {
                                result = xhr.responseXML;
                            } else if (dataType === 'json') {
                                try{
                                    result = blankRE.test(result) ? null : JSON.parse(result);
                                }catch(e){
                                    if(settings.onErrorEval){
                                        result = blankRE.test(result) ? null : eval('('+result+')');
                                    }else{
                                        throw e;
                                    }
                                }
                            }
                        } catch (e) {
                            error = e;
                        }
                        if (error) {
                            return ajaxError(error, 'parsererror', xhr, settings);
                        }
                    }
                    ajaxSuccess(result, xhr, settings);
                    //脚本支持
                    if (dataType === "html") {
                        setTimeout(()=>{
                            let fragment = document.createDocumentFragment();
                            let wrap = document.createElement("div");
                            wrap.innerHTML = result;
                            fragment.appendChild(wrap);
                            let scriptArray = wrap.querySelectorAll("script");                           
                            let loading = [];
                            let jsExeTag = [];
                            let onLoadfN = function(){
                                loading.shift();
                                document.head.removeChild(this);
                            };
                            let max = 1;
                            for (let i = 0; i < scriptArray.length; i++) {
                                let script = scriptArray[i].textContent;     
                                let src = scriptArray[i].src;                                                          
                                let scriptel = document.createElement("script");
                                scriptel.type="text/javascript";
                                if(src){
                                    loading.push(1);
                                    scriptel.onload = onLoadfN;
                                    scriptel.onerror = onLoadfN;
                                    scriptel.src = src;
                                    document.head.appendChild(scriptel);
                                    max++;
                                }else{
                                    scriptel.text = script;
                                    jsExeTag.push(scriptel);
                                }                        
                            }
                            if(jsExeTag.length > 0){
                                let count = 0;
                                max = max * 5 * 4;
                                let ivt = setInterval(()=>{
                                    if( loading.length === 0 || count > max){
                                        clearInterval(ivt);
                                        for(let i =0 ; i < jsExeTag.length ;i++){
                                            document.head.appendChild(jsExeTag[i]);
                                            document.head.removeChild(jsExeTag[i]);
                                        }
                                        jsExeTag =undefined;
                                    }                                   
                                    count++;
                                },300);
                            }
                        },1);
                    }
                } else {
                    ajaxError(xhr.statusText || null, xhr.status ? 'error' : 'abort', xhr, settings);
                }
            }
        };

        if (ajaxBeforeSend(xhr, settings) === false) {
            xhr.abort();
            ajaxError(null, 'abort', xhr, settings);
            return xhr;
        }
        if(settings.onProgress){
            xhr.upload.addEventListener('progress', function (e) {
                var progressRate = Math.round((e.loaded / e.total) * 100 );
                if(progressRate > 100){
                    progressRate = 100;
                }                
                settings.onProgress(progressRate);
            })
        }

        var async = 'async' in settings ? settings.async : true;
        xhr.open(settings.type, settings.url, async, settings.username, settings.password);

        if (settings.xhrFields) {
            let keys = settings.xhrFields;
            keys.forEach(function (key) {
                xhr[key] = settings.xhrFields[key];
            });
        }
        let keys = Object.keys(headers);
        keys.forEach(function (key) {
            nativeSetHeader.apply(xhr, headers[key]);
        });

        if (settings.timeout > 0) {
            let timer = settings.timeout * 1000;
            abortTimeout = setTimeout(() => {
                xhr.onreadystatechange = emptyFn;
                xhr.abort();
                ajaxError("timeout", 'timeout', xhr, settings);
            }, timer);
        }
        let sendData = null;
        if(settings.data){
            sendData = settings.data;
            if(settings.contentType === "application/json"){
                sendData = JSON.stringify(sendData);
            }
        }
        xhr.send(sendData);
        return xhr;
    };
})($B);